home *** CD-ROM | disk | FTP | other *** search
/ The 640 MEG Shareware Studio 2 / The 640 Meg Shareware Studio CD-ROM Volume II (Data Express)(1993).ISO / basic / qbredir.zip / QBREDIR.ASM next >
Assembly Source File  |  1992-03-02  |  18KB  |  434 lines

  1.  
  2. ;                Quick Basic Redirected File Display
  3. ;
  4. ;
  5. ;    ----   A modified version of the original PD program REDVIEW.ASM   ----
  6. ;        by Alexandr Novy and Peter Horak of Prague, Czechoslovakia    
  7. ;
  8. ;
  9. ;    ----   This Public Domain Adaptation for QuickBasic Written By:     
  10. ;
  11. ;                Peter R. Barnes
  12. ;            Brentwood, TN
  13. ;                       March, 1992
  14. ;
  15. ;
  16. ;    ----   To assemble the object module with TASM, use the command:
  17. ;
  18. ;                   TASM QBREDIR   ;I don't have MASM,
  19. ;                                  ;but I'm sure the
  20. ;                                  ;syntax is the same
  21. ;
  22. ;
  23. ;    ----   To use in a QuickBasic program, compile your program
  24. ;           and link the module to your code, for example:
  25. ;
  26. ;                   BC /options YOURPROG;       ;any options you want
  27. ;
  28. ;                   LINK /linkoptions YOURPROG + QBREDIR;
  29. ;
  30. ;
  31. ;
  32. ;    ----   To call the routines in your program, use the following:
  33. ;
  34. ;                   CALL QFRedSet (FlagIntegerVariable%)
  35. ;
  36. ;                   CALL QFRedOff
  37. ;
  38. ;
  39. ;    --- IMPORTANT: READ THE QBREDIR.DOC FILE TO SEE HOW TO
  40. ;                   PROPERLY USE THESE ROUTINES!!  THE ROUTINES
  41. ;                   INSTALL A CUSTOM INTERRUPT HANDLER WHICH
  42. ;                   MUST BE TREATED WITH CARE!!
  43. ;
  44. ;    ----     NOTE: CANNOT BE USED IN THE QB ENVIRONMENT (SEE
  45. ;                   QBREDIR.DOC)
  46. ;
  47. ;
  48. ;    ----   I have  added some comments to help explain the code as
  49. ;           it applies to QuickBasic, just for general (and my own)
  50. ;           reference.  Some of the stuff is pretty elementary, so if
  51. ;           you are an assembly language wiz, just skip it; it's there
  52. ;           for the rest of us.
  53. ;
  54. ;
  55. ;********************************************************************
  56. ;********************************************************************
  57. ;
  58. ; Make our procedure names available to external programs
  59. ;
  60. public QFRedSet, QFRedOff
  61. ;
  62. ;
  63. ;********************************************************************
  64. ;
  65. ; All Basic modules are assembled using the Medium
  66. ; memory model, so we must add the proper assembler directives
  67. ;
  68. ;
  69. .MODEL Medium, Basic               ;always, for QuickBasic
  70. ;
  71. .CODE                              ;make the code segment our default
  72. ;
  73. ;
  74. ;
  75. ; We will let QBasic and TASM set the type of procedure, and we will
  76. ; declare the module using the TASM simplified format
  77. ;
  78. ;
  79. QFRedSet proc  uses si di es ds
  80.                ARG flag:PTR = ARGLEN
  81. ;
  82. ;
  83. ; That tells TASM to add code for a Basic procedure call, save
  84. ; si,di,es,ds pointers, and that we are passing to the procedure a
  85. ; two-byte pointer to the INTEGER (notice that I capitalized that --
  86. ; three guesses why) variable /flag/, consisting of ARGLEN number of
  87. ; bytes -- the QuickBasic 'call' automatically pushes that address onto
  88. ; the stack and TASM calculates the correct offset to those stack
  89. ; bytes when it assembles the code. All parameter addresses passed by
  90. ; QuickBasic are near references (offset only, 1 word or 2 bytes)
  91. ; unless you use the keyword SEG for the passed variable, which tells
  92. ; QuickBasic to pass the address as a far reference (segment:offset,
  93. ; 2 words or 4 bytes).
  94. ;                            ---------
  95. ; TASM knows from our 'Basic' directive that the variable we pass will
  96. ; be a 2-byte address, and that Basic always makes far calls, leaving a full,
  97. ; 2-word return address as the last 4 bytes on the stack; the variable
  98. ; address, next on the stack, will be at [B(ase)P(ointer)+6] from the
  99. ; present BP value (+ because the stack grows downward in memory).
  100. ; That's also how TASM knows that we need a far return, not a near
  101. ; return, and also how many bytes to discard, after it pops that QB
  102. ; return address, to restore the stack after the procedure ends --
  103. ; i.e. 2 bytes for the variable near address...
  104. ;
  105. ;        push  bp                 ;TASM automatically
  106. ;        mov   bp,sp              ;adds the code to save the B(ase)
  107. ;                                 ;P(ointer) to the stack in the stack
  108. ;                                 ;segment, and set up a new stack
  109. ;                                 ;that starts at the end of the old
  110. ;                                 ;one pointed to by the current
  111. ;                                 ;S(tack)P(ointer) -- i.e.
  112.                                   ;   New bp = Old bp + sp
  113. ;                                 ;Actually, our new stack will begin
  114. ;                                 ;at New bp + sp, but this will keep
  115. ;                                 ;us away from the old stack for
  116. ;                                 ;all values of sp, in case sp gets
  117. ;                                 ;set to a lower value -- we will
  118. ;                                 ;never get lower than the new bp.
  119. ;                                 ;Of course, we have to be careful
  120. ;                                 ;to preserve that sp value in our
  121. ;                                 ;code by always 'popping' the same
  122. ;                                 ;number of bytes that we 'push'
  123. ;                                 ;onto the stack.
  124. ;
  125. ;
  126. ;                                 ;This is the code that is added
  127. ;                                 ;by TASM when it sees the 'Basic'
  128. ;         push  si                ;and the 'uses' directives, so we
  129. ;         push  di                ;don't need the explicit statements.
  130. ;         push  es                ;The address of /flag/ is placed on
  131. ;         push  ds                ;the stack first, and ARGLEN tells
  132. ;                                 ;TASM how many bytes to discard, when
  133. ;                                 ;we return from the call, to get
  134. ;                                 ;back to the original stack before
  135. ;                                 ;the call.
  136. ;
  137. ;
  138. ;********************************************************************
  139. ;
  140. ;
  141. ;
  142. ; The first thing we do is grab and save the address of the QB variable
  143. ; that was passed to us, on the stack, by QuickBasic when it made the
  144. ; call; remember that TASM knows that [flag] means [BP+6]. In this
  145. ; call, we passed the 2-byte offset address of the variable in the
  146. ; ds data segment, so the 'word ptr [flag]' will have the 2-byte word
  147. ; for the offset moved into ax; then the 2-byte word for the ds data
  148. ; segment, whatever it is, will be stored. Both of those values are
  149. ; copied into /flagadr/ by the following 3 mov instructions:
  150. ;
  151. ;
  152.          mov   ax,word ptr [flag]  ;save the offset of passed
  153.          mov   cs:flagadr,ax       ;integer variable /flag/ in
  154. ;                                  ;memory location /flagadr/
  155. ;
  156. ;
  157. ; That saves the offset that we passed, now we save the data segment
  158. ; pointer that is in ds at the time of the call. Since QuickBasic
  159. ; only has one data segment for integer variables, ds will already be
  160. ; pointing to the segment containing our /flag/ variable.
  161. ;
  162. ; We could pass the segment pointer when we made the call from
  163. ; QuickBasic by preceding the variable name with the SEG keyword; in
  164. ; that situation, QuickBasic would push the full 4-byte address onto
  165. ; the stack. We could also use the CALLS statement to call this
  166. ; routine, instead of CALL, because that statement always passes the
  167. ; full address of parameters.  But neither of those actions are
  168. ; required, because QuickBasic only has one data segment
  169. ;
  170. ; The following mov instruction saves our data segment for DOS to
  171. ; use to find our variable when it executes the interrupt routine:
  172. ;
  173. ;
  174.          mov   cs:flagadr+2,ds     ;save the segment pointer
  175. ;                                  ;of passed integer variable
  176. ;                                  ;/flag/ in second word
  177. ;                                  ;of /flagadr/ --
  178. ;                                  ;that's what DOS needs when
  179. ;                                  ;the interrupt is called
  180. ;
  181. ;
  182. ; Next step is flagcheck to prevent multiple calls to this routine
  183. ; if it has not been reset.  Without this check, a second call would
  184. ; save our new handler address as the original.
  185. ;
  186. ; Check to see if we have already installed the vector to the
  187. ; interrupt handler routine.  If we have, we don't want to do it
  188. ; again, because we will lose the original vector
  189. ;
  190.          mov   al,cs:[IntSetFlag]  ;get flag to see if handler
  191.          cmp   al,0                ;is already installed
  192.          jne   EndSet              ;if set, we have installed
  193. ;                                  ;so go to exit
  194. ;
  195.          mov   al, 1               ;else set the flag
  196.          mov   cs:[IntSetFlag],al
  197. ;
  198. ;
  199. ;                                  ;pick up original vector contents
  200. ;
  201.          mov   ax,3521H            ;for interrupt 21H (MS-DOS Services
  202.          int   21H                 ;Function Request handler)
  203. ;                                  
  204.          mov   cs:Old21Offset,bx   ;save the original vector
  205.          mov   cs:Old21Segment,es  ;in our data area
  206. ;
  207. ;
  208. ;                                  ;a call to DOS services INT21H for
  209. ;                                  ;the SET INTERRUPT function 25xxH
  210. ;                                  ;requires ds:dx to be set to the
  211. ;                                  ;interrupt address, so the next two
  212. ;                                  ;statements set the ds segment
  213. ;                                  ;equal to the cs segment, where
  214. ;                                  ;our new handler procedure is
  215. ;                                  ;located
  216. ;
  217. ;
  218.          push  cs                  ;push our code segment onto stack
  219.          pop   ds                  ;pop it back off to set ds
  220. ;                                  ;i.e. sets ds=cs
  221. ;
  222.          mov   dx,offset QDualDisp ;load offset of our Int 21 handler
  223.          mov   ax,02521H           ;and reset vector to point to 
  224.          int   21H                 ;our procedure
  225. ;
  226. ;
  227. ;         pop   ds                 ;restore registers and
  228. ;         pop   es                 ;return to our QB program
  229. ;         pop   di
  230. ;         pop   si                 ;NOTE--TASM automatically adds
  231. ;         pop   bp                 ;the code to restore registers
  232. ;
  233. EndSet:  ret
  234. ;
  235. ; actually, it's 'ret [ARGLEN]'    ;TASM automatically makes the
  236. ;                                  ;return discard the /flag/ address
  237. ;                                  ;bytes from the stack--the number
  238. ;                                  ;of bytes to discard was set by
  239. ;                                  ;the ARGLEN directive
  240. ;
  241.  
  242. ;
  243. ;
  244. ;  Following is our new interrupt handler which is called by
  245. ;  MS-DOS whenever an Interrupt 21 MS-DOS Services Request 
  246. ;  is detected.  The procedure first checks our enable flag
  247. ;  to determine if we want the dual output activated; the address of
  248. ;  this flag was passed by the QB program during the call to
  249. ;  the QFRedSet function.  If the flag is not set, we just skip our
  250. ;  handler and proceed to the regular interrupt 21 routine.
  251. ;
  252. ;  The handler then checks to see what type of service is being requested.
  253. ;  If it is a call to the DOS routines to output a character or a string 
  254. ;  to STDOUT, the standard DOS output device, which is redirectible and 
  255. ;  is always designated with file handle 1, then our procedure essentially
  256. ;  duplicates the routine by sending the output to STDERR, the standard
  257. ;  DOS error output device, which is always the display, file handle 2,
  258. ;  and whose output cannot be redirected.  Then the handler exits to the
  259. ;  normal Interrupt 21 procedure to let it process the original call.
  260. ;
  261. ;  Note that the routine is part of the QFRedSet procedure, but it is never
  262. ;  executed when that procedure is called by the QB program, because that 
  263. ;  procedure ends at the 'ret' opcode at EndSet, above.
  264. ;
  265. QDualDisp:
  266.  
  267.          pushf                     ;save everything we can --
  268.          push  ax                  ;i.e. all registers and flags
  269.          push  bx                  
  270.          push  cx           ;note that we save the flags
  271.          push  dx           ;even though DOS did the same    
  272.          push  bp           ;thing when it called the
  273.          push  si           ;interrupt routine
  274.          push  di
  275.  
  276.          
  277.          push  bx                  ;save entering register values     
  278.          push  ds
  279.          mov   bx,cs:flagadr       ;get address of flag value passed
  280.          mov   ds,cs:flagadr+2     ;from our QB program
  281.          mov   al,ds:bx            ;get flag value into al
  282.          pop   ds                  ;retrieve register values     
  283.          pop   bx
  284.  
  285.          or    al, al              ;see if value was zero     
  286.          je    EndOur21            ;yes, don't do anything, go
  287.                                    ;to the regular interrupt 21 routine
  288.      
  289.          cmp   ah,02               ;was it a call to write character to STDOUT
  290.          jne   Int9Chk             ;no, jump to next check
  291.          
  292.          push  ds                  ;yes, save entering data segment value
  293.          push  cs                  ;set ds=cs
  294.          pop   ds
  295.          mov   cs:character,dl     ;save the character to write in character     
  296.          mov   ah,40h              ;set up for int 21 function 40h
  297.                                    ;to write character to output device
  298.          mov   bx,2                ;set file handle=2 for STDERR
  299.          mov   dx,OFFSET character ;get the pointer to the character
  300.          mov   cx,1                ;one character to be written
  301.          pushf                     ;save current flag status
  302.          call  dword ptr cs:[Old21];use old interrupt routine to output
  303.                                    ;the character to the display,
  304.                                    ;it will pop the flags saved on stack
  305.                                    ;when it returns, because it ends with
  306.                                    ;iret, since it is an interrupt routine
  307.          pop   ds                  ;retrieve our character
  308.          jmp   short EndOur21      ;exit to old routine to write the character
  309.                                    ;to redirected output
  310.  
  311. Int9Chk:
  312.          cmp   ah,09               ;was it a call to write string to output     
  313.          jne   Int40Chk            ;no, go to next check
  314.          
  315. Chrloop:                           
  316.          mov   si,dx               ;yes, set index     
  317.          cmp   byte ptr ds:[si],'$';are we at the end of the string
  318.          je    End2109             ;yes, go to finish
  319.          mov   ah,40h              ;no, set up for int 21 function 40h
  320.          mov   bx,2                ;output to STDERR
  321.          mov   cx,1
  322.          push  dx
  323.          pushf
  324.          call  dword ptr cs:[Old21];write the character as above     
  325.          pop   dx
  326.          inc   dx                  ;bump index to next character
  327.          jmp   short Chrloop       ;loop until done
  328. End2109:
  329.          jmp   short EndOur21      ;exit to old routine to write the character
  330.                                    ;to redirected output
  331.  
  332. Int40Chk:
  333.          cmp   ah,40h              ;was it a call to write to file or device     
  334.          jne   EndOur21            ;no, done, exit to regular int 21
  335.                     
  336.          cmp   bx,1                ;yes, then was it a call to STDOUT
  337.          jne   EndOur21            ;no, done, exit
  338.  
  339.          push  ds                  ;yes, save character
  340.          mov   bx,2                ;set file handle for STDERR
  341.          pushf
  342.          call dword ptr cs:[Old21] ;use old interrupt routine to output
  343.          pop   ds                  ;retrieve character
  344.  
  345. EndOur21:
  346.  
  347.                                    ;retrieve registers
  348.          pop   di                  ;this resets registers
  349.          pop   si                  ;and flags to exactly
  350.          pop   bp                  ;where they were when we
  351.          pop   dx                  ;entered our custom handler
  352.          pop   cx
  353.          pop   bx
  354.          pop   ax
  355.          popf
  356. ;           
  357. ;              
  358.          jmp dword ptr cs:[Old21]  ;go to regular int 21 routine                                  
  359. ;
  360. ;
  361. ;
  362. ;
  363. QFRedSet  endp
  364. ;
  365. ;
  366. ;
  367. ;  The subroutine QFRedOff is called by the QB program to
  368. ;  return the MS-DOS interrupt vector 21H to its' original state.
  369. ;  The subroutine is used in the form:
  370. ;
  371. ;                   CALL QFRedOff()
  372. ;
  373. ;
  374. QFRedOff  proc  uses si di es ds   ;Restore MS-DOS Services
  375. ;                                  ;interrupt vector 21H
  376. ;         push  bp                 ;to its' original state
  377. ;         mov   bp,sp
  378. ;         push  ds                 ;same code added as above
  379. ;         etc.
  380. ;
  381. ;
  382. ;  First we must check our IntSetFlag to see if the
  383. ;  vectors to our handler were installed; if not, we 
  384. ;  do not want to reset them here, either
  385. ;
  386. ;
  387.          mov   al,cs:[IntSetFlag]  ;Get our inhibit flag
  388.          cmp   al,1                ;see if it is set
  389.          jne   EndOff              ;no, go to exit
  390.                                    ;else, reset vector
  391. ;
  392. ;
  393.          mov   dx,cs:Old21Offset   ;Set interrupt 21H MS-DOS
  394.          mov   ds,cs:Old21Segment  ;Services Request
  395.          mov   ax,02521H           ;back to its' original vector
  396.          int   21H
  397. ;
  398. ;
  399.          mov   al, 0               ;reset the flag to enable
  400.          mov   cs:[IntSetFlag],al  ;the QFRedSet routine
  401. ;
  402. ;         pop   ds                 ;restore registers and
  403. ;         pop   bp                 ;return to QB program --
  404. ;         etc.                     ;same exit code added as above
  405. ;
  406. EndOff:  ret
  407. ;
  408. QFRedOff endp
  409. ;
  410. ;
  411. ;
  412. IntSetFlag db  0                   ;this flag inhibits QFRedSet operation
  413. ;                                  ;after it has redirected vector
  414. ;                                  ;until QFRedOff resets it, and does
  415. ;                                  ;the same for QFRedOff if QFRedSet
  416. ;                                  ;has not redirected vector
  417. ;
  418. flagadr  dw    0,0                 ;Long address of QB program's
  419. ;                                  ;display redirected output flag --
  420. ;                                  ;note that each '0' is actually 2 bytes
  421. ;
  422. Old21          Label Dword
  423. Old21Offset    dw ?                ;Original contents of MS-DOS
  424. Old21Segment   dw ?                ;Interrupt 21H vector
  425.  
  426. ;
  427. ;
  428. character      DB ?                ;storage for output character
  429. ;
  430.  
  431.          end
  432.  
  433.  
  434.